package cn.com.dmg.myspringboot.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.seg.common.Term;

/**
 * @ClassName PaperCheckingTest
 * @Description 论文查重测试
 * @author zhum
 * @date 2024/4/3 14:18
 */
public class PaperCheckingTest {
    public static void main(String[] args) throws Exception {
        /*

            分词器参考：https://github.com/hankcs/HanLP/tree/1.x
            语义相似度测试：https://www.hanlp.com/semantics/functionapi/similarity?standardType=1
            @author zhum
            @date 2024/4/3 15:12
         */
        String paper1 = "北京到上海的动车票";
        String paper2 = "上海到北京的动车票";

        similarity1(paper1,paper2);
        similarity2(paper1,paper2);

    }

    /**
     * 计算文本相似度1 直接使用分词后出现的频率进行 余弦相似度 的向量计算
     * 参考： https://blog.51cto.com/u_16175434/6815017
     * @author zhum
     * @date 2024/4/3 16:01
     * @param text1
     * @param text2
     * @return double
     */
    public static void similarity1(String text1, String text2) {
        //对论文内容进行分词
        List<String> strSegmentList1 = getStrSegmentList(text1);
        List<String> strSegmentList2 = getStrSegmentList(text2);
        //词频
        Map<String, Integer> wordsFrequency1 = getWordsFrequency(strSegmentList1);
        Map<String, Integer> wordsFrequency2 = getWordsFrequency(strSegmentList2);
        //计算两个文本的余弦相似度
        double v = calculateCosSimilarity(wordsFrequency1, wordsFrequency2);
        System.out.println(v);
    }

    /**
     * 相似度计算2 在词频的基础上使用更复杂的方式进行 余弦相似度 的向量计算
     * 参考：https://www.cnblogs.com/chillsrc/articles/16818399.html
     * @author zhum
     * @date 2024/4/3 16:02
     * @param text1
     * @param text2
     * @return double
     */
    public static void similarity2(String text1, String text2) {
        //对论文内容进行分词
        List<String> strSegmentList1 = getStrSegmentList(text1);
        List<String> strSegmentList2 = getStrSegmentList(text2);
        //词频
        Map<String, Integer> wordsFrequency1 = getWordsFrequency(strSegmentList1);
        Map<String, Integer> wordsFrequency2 = getWordsFrequency(strSegmentList2);
        // 向量化，先并集，然后遍历在并集中对应词语，在自己的分词集合中对应词语出现次数，组成的数就是向量
        Set<String> union = new HashSet<>();
        union.addAll(strSegmentList1);
        union.addAll(strSegmentList2);
        // a、b 一维向量
        int[] aVector = new int[union.size()];
        int[] bVector = new int[union.size()];

        List<String> collect = new ArrayList<>(union);
        for (int i = 0; i < collect.size(); ++i) {
            aVector[i] = wordsFrequency1.getOrDefault(collect.get(i), 0);
            bVector[i] = wordsFrequency2.getOrDefault(collect.get(i), 0);
        }

        //计算两个文本的余弦相似度
        double v = similarity(aVector, bVector);
        System.out.println(v);
    }



    /**
     * 对文本进行分词，并返回分词后的结果
     * @author zhum
     * @date 2024/4/3 15:13
     * @param str
     * @return java.util.List<java.lang.String>
     */
    private static List<String> getStrSegmentList(String str) {
        List<Term> segment = HanLP.segment(str);
        return segment.stream().map(term -> term.word).collect(Collectors.toList());
    }

    /**
     * 获得粗略的tok分词（restFul在线版本）
     *
     * @param str
     * @return java.util.List<java.lang.String>
     * @author zhum
     * @date 2024/4/3 14:41
     */
    private static List getTokCoarse(String str) throws Exception {
        // auth不填则匿名，zh中文，mul多语种
        //HanLPClient hanLP = new HanLPClient("https://www.hanlp.com/api", null, "zh", 10);
        //Map<String, List> parse = hanLP.parse("2021年HanLPv2.1为生产环境带来次世代最先进的多语种NLP技术。阿婆主来到北京立方庭参观自然语义科技公司。");
        List<Term> segment = HanLP.segment("2021年HanLPv2.1为生产环境带来次世代最先进的多语种NLP技术。阿婆主来到北京立方庭参观自然语义科技公司。");

        //Map<String, List> parse = hanLP.parse(str);
        //return parse.get("tok/coarse");
        return null;
    }

    /**
     * 计算文字出现的频率
     * 词袋模型 https://blog.csdn.net/helinnlp/article/details/116358509
     * @author zhum
     * @date 2024/4/3 14:51
     * @param segList
     * @return java.util.Map<java.lang.String,java.lang.Integer>
     */
    private static Map<String, Integer> getWordsFrequency(List<String> segList) {
        Map<String, Integer> wordCountMap = new HashMap<>();
        for (String str : segList) {
            wordCountMap.put(str, wordCountMap.getOrDefault(str, 0) + 1);
        }
        return wordCountMap;
    }



    /**
     * 计算两个文本的余弦相似度
     * @author zhum
     * @date 2024/4/3 14:52
     * @param vec1
     * @param vec2
     * @return double
     */
    private static double calculateCosSimilarity(Map<String, Integer> vec1, Map<String, Integer> vec2) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        for (Map.Entry<String, Integer> entry : vec1.entrySet()) {
            String word = entry.getKey();
            int count = entry.getValue();
            dotProduct += count * vec2.getOrDefault(word, 0);
            norm1 += Math.pow(count, 2);
        }

        for (Map.Entry<String, Integer> entry : vec2.entrySet()) {
            int count = entry.getValue();
            norm2 += Math.pow(count, 2);
        }

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    /**
     * 分别计算三个参数
     * @param aVec a 一维向量
     * @param bVec b 一维向量
     **/
    public static double similarity(int[] aVec, int[] bVec) {
        int n = aVec.length;
        double p1 = 0;
        double p2 = 0f;
        double p3 = 0f;
        for (int i = 0; i < n; i++) {
            p1 += (aVec[i] * bVec[i]);
            p2 += (aVec[i] * aVec[i]);
            p3 += (bVec[i] * bVec[i]);
        }
        p2 = Math.sqrt(p2);
        p3 = Math.sqrt(p3);
        // 结合公式计算
        return (p1) / (p2 * p3);
    }
}
